#include <string.h>
#include <glib.h>
#include "hw/ipmi/imu.h"


void init_imu(const char *filename, imu* imu_920){

	strcpy(imu_920->pcie_file_path, PCIE_FILE);
	cJSON *mock_info = read_json(filename);
	if (!mock_info) return;

	cJSON *imu_version = cJSON_GetObjectItem(mock_info, "Get_imu_version");
	cJSON *imu_version_data = cJSON_GetObjectItem(imu_version, "Data");
	int index =0;
	cJSON *element =NULL;
	cJSON_ArrayForEach(element, imu_version_data) {
		if (index > 1) break;
		imu_920->version[index] = hex_string_to_uint8(element->valuestring);
		index++;
	}

	cJSON *cpu_temp = cJSON_GetObjectItem(mock_info, "Get_cpu_temp");
	if (!cpu_temp) goto cleanup;
	cJSON *cpu_temp_data = cJSON_GetObjectItem(cpu_temp, "Data");
	if (!cpu_temp_data) goto cleanup;

	cJSON *child = cpu_temp_data->child;
	while(child) {
		const char *key = child->string;
		int cpu_id = (int)hex_string_to_uint8(key);
		int temp_index = 0;
		cJSON *temp_element = NULL;
		cJSON_ArrayForEach(temp_element, child) {
			if (temp_index >= 2) break;
			imu_920->cpu_tmp[cpu_id][temp_index] = hex_string_to_uint8(temp_element->valuestring);
			temp_index++;
		}
		child = child->next;
	}
	cJSON *cpu_usage = cJSON_GetObjectItem(mock_info, "Get_cpu_usage");
	if (!cpu_usage) goto cleanup;
	cJSON *cpu_usage_data = cJSON_GetObjectItem(cpu_usage, "Data");
	if (!cpu_usage_data) goto cleanup;

	index = 0;
	cJSON_ArrayForEach(element, cpu_usage_data) {
		if (index > 2) break;
		imu_920->cpu_usage[index] = hex_string_to_uint8(element->valuestring);
		index++;
	}
cleanup:
	cJSON_Delete(mock_info);
}

uint8_t calculate_lrc(const uint8_t *data,int len) {
	uint8_t sum = 0;
	for(int i=0;i<len;i++){
		sum+=data[i];
	}
	return (uint8_t)(-sum);
}

int hex_str_to_bytes(const char* hex_str, uint8_t output[BYTES_NUM]) {
	if (strlen(hex_str) !=ID_LEN) {
		return -1;
	}

	for(int i=0;i<BYTES_NUM;i++){
		char byte_str[3] = {hex_str[i*2], hex_str[i*2+1], '\0'};
		char *endptr;
		long val = strtol(byte_str, &endptr, 16);

		if(*endptr != '\0' || errno == ERANGE) {
			return -1;
		}
		output[i] = (uint8_t)val;
	}
	return 0;
}

int parse_device_id(const char* key, uint8_t id_values[2][BYTES_NUM]) {
	char *key_dup = strdup(key);
	if (key_dup == NULL){
		return -1;
	}
	char *token = strtok(key_dup, "_");
	if (token == NULL) {
		free(key_dup);
		return -1;
	}
	if (hex_str_to_bytes(token, id_values[0])!=0){
		free(key_dup);
		return -1;
	}
	token = strtok(NULL, "_");
	if(token==NULL || hex_str_to_bytes(token, id_values[1])!=0){
		free(key_dup);
		return -1;
	}
	free(key_dup);
	return 0;
}

int mapping_pcie_info(const char* bdf, const mock_infos *pcie_infos, int slot_num){
	for (int i=0;i<slot_num;i++){
		if(strcmp(pcie_infos[i].device_addr, bdf)==0){
			return i;
		}
	}
	return -1;
}

void init_pcie_card(const char * filename, imu *imu_920){
	cJSON *mock_infoes = read_json(filename);
	if (!mock_infoes) return;
	int slot_num=0;
	cJSON *item;
	mock_infos *card=NULL;
	cJSON_ArrayForEach(item, mock_infoes) {
		const char *key = item->string;
		cJSON *values = item;
		if (!cJSON_IsArray(item)) continue;
		card = &(imu_920->pcie_infos[slot_num]);
		card->device_addr[6]="\0";
		strcpy(card->device_addr, cJSON_GetArrayItem(values, 0)->valuestring);
		strcpy(card->device_addr+2, cJSON_GetArrayItem(values, 1)->valuestring);
		strcpy(card->device_addr+4, cJSON_GetArrayItem(values, 2)->valuestring);
		parse_device_id(key, card->id_values);
		slot_num++;
	}
	imu_920->slot_num=slot_num;
}

uint8_t *imu_respond(uint8_t *input, const uint32_t *input_length, uint32_t *output_length, imu* imu_920){

	if (stat(IMU_MOCK_PATH, &imu_920->imu_file_stat) != 0){
		error_report("Get timestamp from %s failed;\n", IMU_MOCK_PATH);
	}
	else if (imu_920->imu_file_stat.st_mtime != imu_920->imu_last_modify){
		warn_report("Detect %s changed, Reload imu;\n", IMU_MOCK_PATH);
		init_imu(IMU_MOCK_PATH, imu_920);
		imu_920->imu_last_modify = imu_920->imu_file_stat.st_mtime;
	}
	if (stat(imu_920->pcie_file_path, &imu_920->file_stat) != 0){
		error_report("Get timestamp from %s failed;\n", imu_920->pcie_file_path);
	}
	else if (imu_920->file_stat.st_mtime != imu_920->last_modify){
		warn_report("Detect %s changed, Reload pcie;\n", imu_920->pcie_file_path);
		init_pcie_card(imu_920->pcie_file_path, imu_920);
		imu_920->last_modify = imu_920->file_stat.st_mtime;
	}
	uint8_t *output = (uint8_t *)malloc(MAX_SIZE * sizeof(uint8_t));
	memset(output,0,MAX_SIZE*sizeof(uint8_t));
	if (input_length < 7){
		output[0]=0xc6;
		*output_length=1;
		return;
	}

	output[0]=input[3];
	output[3]=input[0];
	uint8_t netfn = (input[1] >>2) & 0x3F;
	uint8_t relun = (input[1] & 0x3);
	uint8_t reseq = (input[4] >>2) & 0x3F;
	uint8_t rqlun = (input[4] & 0x3);
	uint8_t cmd = input[5];
	output[1] = ((netfn+1) << 2) | (rqlun &0x03);
	output[4] = (reseq << 2) | (relun &0x03);
	output[2] = calculate_lrc(output,2);
	output[5] = cmd;

	if (netfn == 0x06 && cmd == 0x01){
		output[6]=0x00;
		output[7]=0x0a;
		output[8]=0x01;
		output[9]=imu_920->version[0];
		output[10]=imu_920->version[1];
		output[11]=calculate_lrc(&output[3],8);
		*output_length=12;
		return output;
	}
	if (netfn == 0x30 && cmd == 0x98){
		uint8_t subcmd = input[9];
		switch (subcmd){
			case 0x01:{
				uint8_t cpu_id = input[10];
				output[6]=0x00;
				output[7]=0x00;
				output[8]=0x07;
				output[9]=0xdb;
				output[10]=imu_920->cpu_tmp[cpu_id][0];
				output[11]=imu_920->cpu_tmp[cpu_id][1];
				output[12]=calculate_lrc(&output[3], 9);
				*output_length=13;
				return output;
			}
			default:{
				*output_length=13;
				return output;
			}
		}
	}
	if (netfn == 0x2e && cmd == 0x44){
		output[6]=0x00;
		output[7]=0x00;
		output[8]=0x07;
		output[9]=0xdb;

		uint8_t function = (input[11] >> 4) & 0x07;
		uint8_t d0 = (input[11] >> 7) & 0x01;
		uint8_t d_high = input[12] & 0x0F;
		uint8_t bus_low = (input[12] >>4) & 0x0F;
		uint8_t device = (d_high << 1) | d0;
		uint8_t bus = (input[13] << 4) |bus_low;

		char target_bdf[7];
		snprintf(target_bdf, 7, "%02X%02X%02X", bus, device, function);
		int target_index = mapping_pcie_info(target_bdf, imu_920->pcie_infos, imu_920->slot_num);
		if (target_index == -1){
			*output_length = 15;
			return output;
		}

		const mock_infos *target = &(imu_920->pcie_infos[target_index]);

		uint8_t offset =input[10];
		switch (offset) {
			case 0x00:{ // 获取id;
				output[10]=target->id_values[0][1]; 
				output[11]=target->id_values[0][0]; // 制造商ID，注意字节序
				output[12]=target->id_values[0][3];
				output[13]=target->id_values[0][2]; // 设备ID，注意字节序
				break;
			}
			case 0x2c:{ // 获取auxid;
				output[10]=target->id_values[1][1];
				output[11]=target->id_values[1][0]; // 子厂商ID，注意字节序
				output[12]=target->id_values[1][3];
				output[13]=target->id_values[1][2]; // 子设备ID，注意字节序
				break;
			}
		}
		output[14]=calculate_lrc(&output[3],11);
		*output_length=15;
		return output;
	}
	else{
		*output_length=16;
		return output;
	}
	return NULL;
}

void debug_pcie_card(imu *imu_920){
	int slotnum = imu_920->slot_num;
	mock_infos *now_slot=imu_920->pcie_infos;
	error_report("slot total:%d", slotnum);
	for (int i=0;i<slotnum;i++){
		error_report("bdf: %s", now_slot[i].device_addr);
		error_report("id: %02X%02X%02X%02X", now_slot[i].id_values[0][0], now_slot[i].id_values[0][1], now_slot[i].id_values[0][2], now_slot[i].id_values[0][3]);
		error_report("auxid: %02X%02X%02X%02X", now_slot[i].id_values[1][0], now_slot[i].id_values[1][1], now_slot[i].id_values[1][2], now_slot[i].id_values[1][3]);
	}
}
